package com.blog.blog.service.impl;

import com.blog.blog.service.BackupService;
import com.blog.blog.service.oss.OssService;
import com.blog.blog.vo.OssFileVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 数据备份服务实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class BackupServiceImpl implements BackupService {
    
    private final OssService ossService;
    
    @Value("${spring.datasource.url}")
    private String databaseUrl;
    
    @Value("${spring.datasource.username}")
    private String databaseUsername;
    
    @Value("${spring.datasource.password}")
    private String databasePassword;
    
    @Value("${app.upload.path:/tmp/uploads}")
    private String uploadPath;
    
    private static final String BACKUP_PREFIX = "backups/";
    
    @Override
    @Async
    public OssFileVO backupDatabase() {
        try {
            String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
            String backupFileName = "database_backup_" + timestamp + ".sql";
            
            // 创建临时文件
            Path tempFile = Files.createTempFile("db_backup_", ".sql");
            
            try {
                // 根据数据库类型选择备份命令
                boolean success;
                if (databaseUrl.contains("postgresql")) {
                    success = backupPostgreSQL(tempFile.toString());
                } else if (databaseUrl.contains("mysql")) {
                    success = backupMySQL(tempFile.toString());
                } else {
                    log.error("不支持的数据库类型: {}", databaseUrl);
                    return null;
                }
                
                if (!success) {
                    return null;
                }
                
                // 读取备份文件并上传到OSS
                byte[] backupData = Files.readAllBytes(tempFile);
                String ossKey = BACKUP_PREFIX + "database/" + backupFileName;
                
                OssFileVO result = ossService.upload(backupData, ossKey, "application/sql");
                log.info("数据库备份已上传到OSS: {}", result.getUrl());
                return result;
                
            } finally {
                // 删除临时文件
                Files.deleteIfExists(tempFile);
            }
            
        } catch (Exception e) {
            log.error("数据库备份失败", e);
            return null;
        }
    }
    
    private boolean backupPostgreSQL(String backupFilePath) {
        try {
            // 从URL中提取数据库信息
            String dbUrl = databaseUrl.substring(databaseUrl.lastIndexOf("/") + 1);
            if (dbUrl.contains("?")) {
                dbUrl = dbUrl.substring(0, dbUrl.indexOf("?"));
            }
            String host = extractHost(databaseUrl);
            String port = extractPort(databaseUrl);
            
            ProcessBuilder pb = new ProcessBuilder(
                "pg_dump",
                "-h", host,
                "-p", port,
                "-U", databaseUsername,
                "-d", dbUrl,
                "-f", backupFilePath,
                "--no-password"
            );
            
            // 设置环境变量
            pb.environment().put("PGPASSWORD", databasePassword);
            
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode == 0) {
                log.info("PostgreSQL数据库备份成功: {}", backupFilePath);
                return true;
            } else {
                log.error("PostgreSQL数据库备份失败，退出码: {}", exitCode);
                return false;
            }
            
        } catch (Exception e) {
            log.error("PostgreSQL数据库备份失败", e);
            return false;
        }
    }
    
    private boolean backupMySQL(String backupFilePath) {
        try {
            String dbUrl = databaseUrl.substring(databaseUrl.lastIndexOf("/") + 1);
            if (dbUrl.contains("?")) {
                dbUrl = dbUrl.substring(0, dbUrl.indexOf("?"));
            }
            String host = extractHost(databaseUrl);
            String port = extractPort(databaseUrl);
            
            ProcessBuilder pb = new ProcessBuilder(
                "mysqldump",
                "-h", host,
                "-P", port,
                "-u", databaseUsername,
                "-p" + databasePassword,
                "--single-transaction",
                "--routines",
                "--triggers",
                dbUrl
            );
            
            pb.redirectOutput(new File(backupFilePath));
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode == 0) {
                log.info("MySQL数据库备份成功: {}", backupFilePath);
                return true;
            } else {
                log.error("MySQL数据库备份失败，退出码: {}", exitCode);
                return false;
            }
            
        } catch (Exception e) {
            log.error("MySQL数据库备份失败", e);
            return false;
        }
    }
    
    @Override
    @Async
    public OssFileVO backupFiles() {
        try {
            String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
            String backupFileName = "files_backup_" + timestamp + ".zip";
            
            Path uploadDir = Paths.get(uploadPath);
            if (!Files.exists(uploadDir)) {
                log.warn("上传目录不存在: {}", uploadPath);
                return null;
            }
            
            // 创建临时ZIP文件
            Path tempFile = Files.createTempFile("files_backup_", ".zip");
            
            try {
                try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(tempFile))) {
                    Files.walk(uploadDir)
                        .filter(Files::isRegularFile)
                        .forEach(file -> {
                            try {
                                String entryName = uploadDir.relativize(file).toString();
                                ZipEntry entry = new ZipEntry(entryName);
                                zos.putNextEntry(entry);
                                Files.copy(file, zos);
                                zos.closeEntry();
                            } catch (IOException e) {
                                log.error("添加文件到备份失败: {}", file, e);
                            }
                        });
                }
                
                // 读取ZIP文件并上传到OSS
                byte[] zipData = Files.readAllBytes(tempFile);
                String ossKey = BACKUP_PREFIX + "files/" + backupFileName;
                
                OssFileVO result = ossService.upload(zipData, ossKey, "application/zip");
                log.info("文件备份已上传到OSS: {}", result.getUrl());
                return result;
                
            } finally {
                // 删除临时文件
                Files.deleteIfExists(tempFile);
            }
            
        } catch (Exception e) {
            log.error("文件备份失败", e);
            return null;
        }
    }
    
    @Override
    @Async
    public OssFileVO fullBackup() {
        try {
            String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
            String fullBackupFileName = "full_backup_" + timestamp + ".zip";
            
            // 先执行数据库备份和文件备份
            OssFileVO dbBackup = backupDatabase();
            OssFileVO filesBackup = backupFiles();
            
            if (dbBackup == null && filesBackup == null) {
                log.error("数据库和文件备份都失败");
                return null;
            }
            
            // 创建临时完整备份文件
            Path tempFile = Files.createTempFile("full_backup_", ".zip");
            
            try {
                try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(tempFile))) {
                    
                    // 添加数据库备份
                    if (dbBackup != null) {
                        byte[] dbData = ossService.download(dbBackup.getKey());
                        ZipEntry dbEntry = new ZipEntry("database/" + Paths.get(dbBackup.getKey()).getFileName().toString());
                        zos.putNextEntry(dbEntry);
                        zos.write(dbData);
                        zos.closeEntry();
                    }
                    
                    // 添加文件备份
                    if (filesBackup != null) {
                        byte[] filesData = ossService.download(filesBackup.getKey());
                        ZipEntry filesEntry = new ZipEntry("files/" + Paths.get(filesBackup.getKey()).getFileName().toString());
                        zos.putNextEntry(filesEntry);
                        zos.write(filesData);
                        zos.closeEntry();
                    }
                }
                
                // 读取完整备份文件并上传到OSS
                byte[] fullBackupData = Files.readAllBytes(tempFile);
                String ossKey = BACKUP_PREFIX + "full/" + fullBackupFileName;
                
                OssFileVO result = ossService.upload(fullBackupData, ossKey, "application/zip");
                log.info("完整备份已上传到OSS: {}", result.getUrl());
                return result;
                
            } finally {
                // 删除临时文件
                Files.deleteIfExists(tempFile);
            }
            
        } catch (Exception e) {
            log.error("完整备份失败", e);
            return null;
        }
    }
    
    @Override
    public List<OssFileVO> listBackupFiles() {
        try {
            return ossService.listObjects(BACKUP_PREFIX);
        } catch (Exception e) {
            log.error("获取备份文件列表失败", e);
            return List.of();
        }
    }
    
    @Override
    public void cleanupOldBackups(int retentionDays) {
        try {
            List<OssFileVO> backupFiles = listBackupFiles();
            long cutoffTime = System.currentTimeMillis() - (retentionDays * 24L * 60 * 60 * 1000);
            
            for (OssFileVO file : backupFiles) {
                if (file.getLastModified() != null && file.getLastModified().getTime() < cutoffTime) {
                    try {
                        ossService.delete(file.getKey());
                        log.info("删除过期备份文件: {}", file.getKey());
                    } catch (Exception e) {
                        log.error("删除过期备份文件失败: {}", file.getKey(), e);
                    }
                }
            }
                
        } catch (Exception e) {
            log.error("清理过期备份失败", e);
        }
    }
    
    @Override
    public boolean restoreDatabase(String ossKey) {
        try {
            // 从OSS下载备份文件
            byte[] backupData = ossService.download(ossKey);
            
            // 创建临时文件
            Path tempFile = Files.createTempFile("db_restore_", ".sql");
            
            try {
                Files.write(tempFile, backupData);
                
                if (databaseUrl.contains("postgresql")) {
                    return restorePostgreSQL(tempFile.toString());
                } else if (databaseUrl.contains("mysql")) {
                    return restoreMySQL(tempFile.toString());
                } else {
                    log.error("不支持的数据库类型: {}", databaseUrl);
                    return false;
                }
                
            } finally {
                // 删除临时文件
                Files.deleteIfExists(tempFile);
            }
            
        } catch (Exception e) {
            log.error("数据库恢复失败", e);
            return false;
        }
    }
    
    @Override
    public boolean deleteBackupFile(String ossKey) {
        try {
            ossService.delete(ossKey);
            log.info("删除备份文件成功: {}", ossKey);
            return true;
        } catch (Exception e) {
            log.error("删除备份文件失败: {}", ossKey, e);
            return false;
        }
    }
    
    private boolean restorePostgreSQL(String backupFilePath) {
        try {
            String dbUrl = databaseUrl.substring(databaseUrl.lastIndexOf("/") + 1);
            if (dbUrl.contains("?")) {
                dbUrl = dbUrl.substring(0, dbUrl.indexOf("?"));
            }
            String host = extractHost(databaseUrl);
            String port = extractPort(databaseUrl);
            
            ProcessBuilder pb = new ProcessBuilder(
                "psql",
                "-h", host,
                "-p", port,
                "-U", databaseUsername,
                "-d", dbUrl,
                "-f", backupFilePath
            );
            
            pb.environment().put("PGPASSWORD", databasePassword);
            
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode == 0) {
                log.info("PostgreSQL数据库恢复成功: {}", backupFilePath);
                return true;
            } else {
                log.error("PostgreSQL数据库恢复失败，退出码: {}", exitCode);
                return false;
            }
            
        } catch (Exception e) {
            log.error("PostgreSQL数据库恢复失败", e);
            return false;
        }
    }
    
    private boolean restoreMySQL(String backupFilePath) {
        try {
            String dbUrl = databaseUrl.substring(databaseUrl.lastIndexOf("/") + 1);
            if (dbUrl.contains("?")) {
                dbUrl = dbUrl.substring(0, dbUrl.indexOf("?"));
            }
            String host = extractHost(databaseUrl);
            String port = extractPort(databaseUrl);
            
            ProcessBuilder pb = new ProcessBuilder(
                "mysql",
                "-h", host,
                "-P", port,
                "-u", databaseUsername,
                "-p" + databasePassword,
                dbUrl
            );
            
            pb.redirectInput(new File(backupFilePath));
            Process process = pb.start();
            int exitCode = process.waitFor();
            
            if (exitCode == 0) {
                log.info("MySQL数据库恢复成功: {}", backupFilePath);
                return true;
            } else {
                log.error("MySQL数据库恢复失败，退出码: {}", exitCode);
                return false;
            }
            
        } catch (Exception e) {
            log.error("MySQL数据库恢复失败", e);
            return false;
        }
    }
    
    private String extractHost(String url) {
        try {
            String temp = url.substring(url.indexOf("://") + 3);
            if (temp.contains("@")) {
                temp = temp.substring(temp.indexOf("@") + 1);
            }
            return temp.substring(0, temp.indexOf(":"));
        } catch (Exception e) {
            return "localhost";
        }
    }
    
    private String extractPort(String url) {
        try {
            String temp = url.substring(url.indexOf("://") + 3);
            if (temp.contains("@")) {
                temp = temp.substring(temp.indexOf("@") + 1);
            }
            temp = temp.substring(temp.indexOf(":") + 1);
            return temp.substring(0, temp.indexOf("/"));
        } catch (Exception e) {
            return databaseUrl.contains("postgresql") ? "5432" : "3306";
        }
    }
}
